<?php
  /**
   * ephem.php
   *
   * Very simple ephemeris for PHP.
   */

  /**
   * Convert system time in milliseconds (time()*1000), microtime()/1000) to Julian Days.
   */
  function time_to_JD( $t )
  {
    return $t / 86400000.0 + 2440587.5;
  }
  /**
   * Convert Julian Days to system time in milliseconds.
   */
  function JD_to_time( $jd )
  {
    return ($jd - 2440587.5) * 86400000;
  }
  /**
   * Look up a single planet.  Returns a negative number if the
   * requested time was out or range or if the requested body was not found.
   */
  function lookup( $JD, $body )
  {
    // establish filename and open file
    if ($body == "moon")
    {
      if ($JD > 2451910.5)
        $filename = $body . "2.lng";
      else
        $filename = $body . "1.lng";
    }
    else
      $filename = $body . ".lng";
    $fd = fopen( dirname(__FILE__) . "/bodies/" . $filename, "r" );
    if (! $fd)
      return -1;
    // read the header values
    $jd0 = floatval( fread( $fd, 16 ) );
    $dt = floatval( fread( $fd, 16 ) );
    $jd1 = floatval( fread( $fd, 16 ) );
    if ($JD < $jd0  ||  $JD > $jd1)
    {
      fclose( $fd );
      return -1;
    }
    // determine which record we need
    $rec0 = ($JD - $jd0) / $dt;
    $rec_int = floor( $rec0 );
    $rec_fr = $rec0 - $rec_int;
    // read two samples and close file
    fseek( $fd, 64 + $rec_int * 8 );
    $sample0 = floatval( fread( $fd, 8 ) );
    $sample1 = floatval( fread( $fd, 8 ) );
    fclose( $fd );
    // determine normalized delta between samples
    $delta = $sample1 - $sample0;
    if ($delta < -180)
      $delta += 360;
    if ($delta > 180)
      $delta -= 360;
    // interpolate and normalize
    $longitude = $sample0 + $delta * $rec_fr;
    if ($longitude < 0)
      $longitude += 360;
    if ($longitude >= 360)
      $longitude -= 360;
    // return resulting longitude in degrees
    return $longitude;
  }

  /**
   * Get list of supported planet names.
   */
  function getPlanetNames()
  {
    return array( "sun", "moon", "mercury", "venus", "mars", "jupiter", "saturn", "uranus", "neptune", "pluto" );
  }
  /**
   * Get an associative array with longitudes for all of the planets.
   */
  function getPlanets( $JD )
  {
    $out = array();
    $planets = getPlanetNames();
    foreach ($planets as $pl)
      $out[$pl] = lookup( $JD, $pl );
    return $out;
  }
  
  /**
   * Determine angular separation between two points.
   */
  function angleDelta( $a1, $a2 )
  {
    $d = abs( $a2 - $a1 );
    if ($d < -180)
      return $d + 360;
    if ($d > 180)
      return 360 - $d;
    return $d;
  }
  
  /**
   * Find aspects within a chart.
   */
  function aspects( $p1 )
  {
  	return synastry( $p1, $p1 );
  }
  
  /**
   * Quick synastry between two sets of planets.
   */
  function synastry( $p1, $p2 )
  {
  	$sameChart = ($p1 == $p2);
    $out = array();
    foreach ($p1 as $p1name => $p1value)
      foreach( $p2 as $p2name => $p2value)
      {
      	// this is a cheap way to eliminate duplicate aspects
      	if ($sameChart  &&  $p1value >= $p2value)
      	  continue;
        //if ($p1name == "moon"  ||  $p2name == "moon")
        //  continue;
        if ($p1name == "pluto"  ||  $p2name == "pluto")
          continue;
        $d = angleDelta( $p1value, $p2value );
        $type = -1;
        if ($d < 8)
          $type = 0;
        else if ($d > 84 && $d < 96)
          $type = 90;
        else if ($d > 114 && $d < 126)
          $type = 120;
        else if ($d > 173)
          $type = 180;
        if ($type >= 0)
        {
          $asp = array( "p1" => $p1name, "p2" => $p2name, "type" => $type );
          $asp['orb'] = abs($d - $type);
          $asp['sig'] = 1 / max( 0.05, $asp['orb'] );
          if ($type > 0)
            $asp['sig'] /= 360/$type;
          $out[] = $asp;
        }
      }
    usort( $out, "syn_cmp" );
    return $out;
  }
  
  /**
   * Calculate transits to natal planets.
   * #1 - natal
   * #2 - transiting
   */
  function transits( $p1, $t2 )
  {
    $skip1 = array( "moon" => 0, "pluto" => 1 );
    $skip2 = array( "sun" => 1, "mercury" => 1, "moon" => 1, "pluto" => 1, "venus" => 1, "mars" => 1 );
  	
  	$p2h = array();
  	$midH = 8;
  	$dts = array();
  	for ($h=-$midH; $h <= $midH; $h++)
  	{
  	  if ($h == 0)
  	    $dt = 0;
  	  else
  	    $dt = (($h < 0) ? -1 : 1) * 5 * pow( 2, abs($h) );
  	  $p2h[] = getPlanets( $t2 + $dt );
  	  $dts[] = $dt;
  	}
  	$p2 = $p2h[$midH];
  	
    $out = array();
    foreach ($p1 as $p1name => $p1value)
    {
      if ($skip1[ $p1name ])
        continue;
      foreach( $p2 as $p2name => $p2value)
      {
      	if ($skip2[ $p2name ])
          continue;
        $d = angleDelta( $p1value, $p2value );
        $type = -1;
        if ($d < 6)
        { $type = 0; $max = 6; }
        else if ($d > 86 && $d < 94)
        { $type = 90; $max = 4; }
        else if ($d > 116 && $d < 124)
        { $type = 120; $max = 4; }
        else if ($d > 175)
        { $type = 180; $max = 5; }
        if ($type >= 0)
        {
          $asp = array( "p1" => $p1name, "p2" => $p2name, "type" => $type );
          $asp['orb'] = abs($d - $type);
          $asp['sig'] = 1 / max( 0.05, $asp['orb'] );
          if ($type > 0)
            $asp['sig'] /= 360/$type;
          // check for nearby start/end of aspect
          $durL = 0;
          $durR = 0;
          for ($h=$midH; $h >= 1; $h--)
          {
          	if ($durL == 0)
              if (abs( angleDelta( $p1value, $p2h[$midH-$h][$p2name] ) - $type ) < $max)
              	$durL = ($h==$midH) ? 0 : $dts[$midH-$h-1];
          	if ($durR == 0)
              if (abs( angleDelta( $p1value, $p2h[$midH+$h][$p2name] ) - $type ) < $max)
                $durR = ($h==$midH) ? 0 : $dts[$midH+$h+1];
          }
          $asp['started'] = $durL;
          $asp['ends'] = $durR;
          // store aspect info
          $out[] = $asp;
        }
      }
    }
    usort( $out, "syn_cmp" );
    return $out;
  }

  // sort function for aspects - sorts by significance
  function syn_cmp( $a1, $a2 )
  {
    if ($a1['sig'] == $a2['sig'])
      return 0;
    if ($a1['sig'] > $a2['sig'])
      return -1;
    else
      return 1;
  }
  
  /**
   * Get an interpretation.
   */
  function interpretation( $type, $planet1, $aspect, $planet2 )
  {
  	$asp = sprintf( "%03d", $aspect );
  	$filename = dirname( __FILE__ ) . "/writing/" . $type . "/" . substr($planet1,0,3) . "_" . $asp . "_" . substr($planet2,0,3) . ".txt";
  	if (! file_exists( $filename ))
  	  return "";
  	else
  	  return file_get_contents( $filename );
  }
  
  /**
   * Describe an amount of time in very rough terms.
   */
  function approxTime( $delta, $future )
  {
  	$delta = abs( $delta );
  	if ($delta == 0  ||  $delta > 1280)
  	  $d = "several years";
  	else if ($delta > 640)
  	  $d = "a few years";
  	else if ($delta > 320)
  	  $d = "a year or so";
  	else if ($delta > 160)
  	  $d = "several months";
  	else if ($delta > 80)
  	  $d = "a few months";
  	else if ($delta > 40)
  	  $d = "several weeks";
  	else if ($delta > 20)
  	  $d = "a few weeks";
  	else if ($delta > 10)
  	  $d = "a couple weeks";
  	else
  	  $d = "several days";
  	return $future ? "in " . $d : $d . " ago";
  }
  
?>

<?php
/*
Synastry...
<br/>
<pre>
<?php
  //$p1 = getPlanets( time_to_JD( time()*1000 ) );
  $p1 = getPlanets( time_to_JD( mktime(14,0,0,8,22,1967)*1000 ) );
  $p2 = getPlanets( time_to_JD( mktime(14,9,0,4,4,1967)*1000 ) );
  $syn = synastry( $p1, $p2 );
  print_r( $syn );
  print_r( $p1 );
  print_r( $p2 );
?>
</pre>
*/
?>


